#pragma once

#include <vex/iterable/contract/range.h>
#include <vex/core/as_intrusive.h>

namespace tests {
    namespace vi = vex::iterable;
    namespace vic = vex::iterable::contract;

    class forward_intrusive_range_call {
        int m_client_called;
    public:
        forward_intrusive_range_call() : m_client_called(0) {}

        // Watch out: here, if an intrusive pointer of more derived type is passed to "call"
        // the conversion is not considered during the overload candidate selection...
        int call_trivial(boost::intrusive_ptr< vic::range<> > const & p_range) 
        {
            p_range->move_front();
            return ++m_client_called;
        }

        template< class TRef>
        int call(boost::intrusive_ptr< vic::range<vex::iterable::output, TRef> > const & p_range) 
        {
            if (p_range->move_front()) {
                p_range->put(typename std::decay<TRef>::type());
            }
            ++m_client_called;
            return this->call_trivial(boost::static_pointer_cast< vic::range<> >(p_range));
        }

        template< class TRef>
        int call(boost::intrusive_ptr< vic::range<vex::iterable::one_pass, TRef> > const & p_range) 
        {
            if (p_range->move_front()) {
                p_range->front();
            }
            ++m_client_called;
            
            return this->call_trivial(boost::static_pointer_cast< vic::range<> >(p_range));
        }

        template< class TRef>
        int call(boost::intrusive_ptr< vic::range<vex::iterable::forward, TRef> > const & p_range) 
        {
            if (auto t_saved = vex::as_intrusive_from_out(p_range->save())) {
                t_saved->move_front();
            }

            ++m_client_called;
            
            return this->call(boost::static_pointer_cast< vic::range<vex::iterable::one_pass, TRef> >(p_range));
        }

        template< class TRef>
        int call(boost::intrusive_ptr< vic::range<vex::iterable::bidirectional, TRef> > const & p_range) 
        {
            if (p_range->move_back()) {
                p_range->back();
            }

            ++m_client_called;
            
            return this->call(boost::static_pointer_cast< vic::range<vex::iterable::forward, TRef> >(p_range));
        }

        template< class TRef>
        int call(boost::intrusive_ptr< vic::range<vex::iterable::random_access, TRef> > const & p_range) 
        {
            if (p_range->size() > 0u) {
                p_range->at(0);
            }

            ++m_client_called;
            
            return this->call(boost::static_pointer_cast< vic::range<vex::iterable::bidirectional, TRef> >(p_range));
        }
    };
}